
#include "lwip/stats.h"
#include "lwip/snmp.h"
#include "lwip/mem.h"
#include "netif/etharp.h"
#include "tile_if.h"


#define IFNAME0 'e'
#define IFNAME1 't'
static unsigned char netio_test_mac[] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55};



err_t tile_if_init(struct netif *netif)
{
    struct tile_if *tile_if;

    tile_if = mem_malloc(sizeof(struct tile_if));
    if (tile_if == NULL)
    {
        LWIP_DEBUGF(NETIF_DEBUG, ("tile_if_init: out of memory for tile_if\n"));
        return ERR_MEM;
    }
    netif->state = tile_if;

    netif->hwaddr_len = ETHARP_HWADDR_LEN;
    netif->name[0] = IFNAME0;
    netif->name[1] = IFNAME1;
    netif->output = etharp_output;
    netif->linkoutput = netio_output;
    netif->mtu = 1500;
    netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;

    tile_if->ethaddr = (struct eth_addr *) & (netif->hwaddr[0]);

    memcpy((char *)tile_if->ethaddr, (char *)netio_test_mac, 6);
    /* netio init */
    tile_if->netio_instance = if_netio_init(netif->netio_ifname, netif->worker_id);
    if (tile_if->netio_instance == NULL)
    {
        LWIP_DEBUGF(NETIF_DEBUG, ("failed to init netio\n"));
        netif->state = NULL;
        mem_free(tile_if);
        return ERR_IF;
    }

    return ERR_OK;
}

void tile_if_setup_flow (struct netif *netif, unsigned int workers)
{
    struct tile_if *tile_if = netif->state;
    netio_instance_t *instance;

    if (!tile_if || !(instance = tile_if->netio_instance))
    {
        tmc_task_die("\n%s: Error, uninitialized interface.", __FUNCTION__);
    }

    flow_config(instance, workers);
}

void tile_if_start(struct netif *netif)
{
    struct tile_if *tile_if = netif->state;
    netio_instance_t *instance;

    if (!tile_if || !(instance = tile_if->netio_instance))
    {
        tmc_task_die("\n%s: Error, uninitialized interface.", __FUNCTION__);
    }

    netio_input_initialize(&instance->queue);

}

void dump_packet(char *buf, int len)
{
#define MAX_DUMP_EN 60
    int dlen = MAX_DUMP_EN > len ? len : MAX_DUMP_EN;

    for (int i = 0; i < dlen; i++)
    {
        char a = buf[i] & 0xF;
        char b = (buf[i] >> 4) & 0xF;
        printf("[%x%x] ", b, a);
    }

    printf("\n");

}


struct pbuf *netio_input(struct netif *netif)
{
    struct pbuf *p;
    struct tile_if *tile_if;
    netio_instance_t *instance;
    int len = 0;
    netio_pkt_t packet;
    netio_pkt_metadata_t *mda;
    char *buf;

    tile_if = netif->state;

    if (!tile_if || !(instance = tile_if->netio_instance))
    {
        printf("\n%s: Error, uninitialized interface.", __FUNCTION__);
        return NULL;
    }

    if (netio_get_packet(&instance->queue, &packet) == NETIO_PKT)
    {
        mda = NETIO_PKT_METADATA(&packet);
        len = NETIO_PKT_L2_LENGTH_M(mda, &packet);
        buf = (char *)NETIO_PKT_L2_DATA_M(mda, &packet);
        instance->rx_counter++;
    }
    else
    {
        //printf("no packet got\n");
        //sleep(1);
        return NULL;
    }

    //dump_packet(buf, len);

#if ETH_PAD_SIZE
    len += ETH_PAD_SIZE; /* allow room for Ethernet padding */
#endif
    p = pbuf_alloc(PBUF_RAW, len, PBUF_RAM);

    if (p != NULL)
    {
#if ETH_PAD_SIZE
        pbuf_header(p, -ETH_PAD_SIZE); /* drop the padding word */
#endif

        for (struct pbuf *q = p; q != NULL; q = q->next)
        {
            memcpy(q->payload, buf, q->len);
            buf += q->len;
        }

#if ETH_PAD_SIZE
        pbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */
#endif
    }
    else
    {
        /* drop packet(); */
        printf("Could not allocate pbufs\n");
    }
    netio_free_buffer(&instance->queue, &packet);

    return p;
}


err_t netio_output(struct netif *netif, struct pbuf *p)
{
    struct tile_if *tile_if = netif->state;
    netio_instance_t *instance;
    struct pbuf *q;
    char *data, *tmp;
    int l2_len;
    netio_error_t err;
    netio_queue_t *queue;
    netio_pkt_t packet;
    netio_pkt_minimal_metadata_t *mmd;
    unsigned int retry_limit = 1;

    if (!tile_if || !(instance = tile_if->netio_instance))
    {
        printf("\n%s: Error, uninitialized interface.", __FUNCTION__);
        return ERR_VAL;
    }

    queue = &instance->queue;

#if ETH_PAD_SIZE
    pbuf_header(p, -ETH_PAD_SIZE); /* drop the padding word */
#endif

    l2_len = p->tot_len;

    err = netio_get_buffer(queue, &packet, l2_len, 0);
    if (err != NETIO_NO_ERROR)
    {
#ifdef NETIO_DEBUG
        printf("couldn't send packet, status %d %d: %s",
               l2_len, err, netio_strerror(err));
#endif
        instance->tx_err_counter++;
        return ERR_OK;
    }

    netio_populate_buffer(&packet);
    mmd = NETIO_PKT_MINIMAL_METADATA(&packet);
    NETIO_PKT_SET_L2_LENGTH_MM(mmd, &packet, l2_len);
    data = (char *)NETIO_PKT_L2_DATA_MM(mmd, &packet);

    tmp = data;
    for (q = p; q != NULL; q = q->next)
    {
        memcpy(tmp, q->payload, q->len);
        tmp += q->len;

        if (q->len == q->tot_len)
        {
            LWIP_ASSERT("only single packet in queue allowed!", (q->next == NULL));
        }

    }

#if ETH_PAD_SIZE
    pbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */
#endif
    netio_pkt_finv(data, l2_len);
    netio_pkt_fence();

    do
    {
        err = netio_send_packet(queue, &packet);
        if (err == NETIO_NO_ERROR)
        {
            break;
        }
        if (err != NETIO_QUEUE_FULL)
        {
            /* failed with other error. */
            printf("couldn't send packet, status %d: %s",
                   err, netio_strerror(err));
            netio_free_buffer(queue, &packet);
            instance->tx_err_counter++;
            return ERR_OK;
        }
        instance->tx_retry_counter++;
    }
    while (--retry_limit);

    if (retry_limit)
    {
        instance->tx_counter++;
    }
    else
    {
#ifdef NETIO_DEBUG
        printf("%s: Send busy", __FUNCTION__);
#endif
        netio_free_buffer(queue, &packet);
        return ERR_OK;
    }

    return ERR_OK;
}


void
tile_if_input(struct netif *netif)
{
    struct pbuf *p;

    p = netio_input(netif);
    if (p != NULL)
    {
#if LINK_STATS
        lwip_stats.link.recv++;
#endif /* LINK_STATS */

        if (ERR_OK != netif->input(p, netif))
            pbuf_free(p);

    }
}


void tile_if_st_show(struct netif *netif)
{
    struct tile_if *tile_if = netif->state;
    netio_instance_t *instance;

    if (!tile_if || !(instance = tile_if->netio_instance))
    {
        tmc_task_die("\n%s: Error, uninitialized interface.", __FUNCTION__);
    }

    if_netio_show_stat(instance);

}